iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0

Day26、Day27 陸續把 /new-postmortem/weekly-report 疊上 obsidian-agent-brain,但到目前為止,gofmt/go vet/go testMakefilecheck 目標)跟 brain health,都還是「想到才會手動跑」的本機指令,brain graph --json(Day21)的輸出也只存在於執行當下的終端機視窗裡,關掉就沒了。obsidian-agent-brain 其實是一個真的有 GitHub remote 的專案,這件事前 27 天都沒特別派上用場——今天要用。

obsidian-agent-brain 累積到 Day27 為止,驗證程式碼品質跟 vault 健康度靠的是自己記得跑 make checkbrain health;想看知識圖譜長什麼樣,就自己跑一次 brain graph --json,看完關掉終端機就沒了。今天要做的事情很純粹:把「push 就自動驗證」「順手留一份圖譜快照」交給 GitHub Actions。刻意不新增任何 brain-cli 程式碼——因為驗證跟圖譜產出所需要的指令(gofmtgo vetgo testbrain healthbrain graph --json)全部已經存在,CI 要做的只是「自動幫你按下這些指令」。

validate job 不透過 make check,直接展開四個指令

第一個念頭是讓 CI 直接呼叫 make check——Makefile 都寫好了,何必重複一次。但 check 目標其實是 fmt vet test 三個目標的組合,不含 health;更關鍵的是 fmt 目標本身只是 gofmt -l .,列出格式不符的檔案,但不會因為有輸出就讓 shell 結束碼變非零。如果直接呼叫 make checkgofmt 抓到的格式問題完全不會讓 CI 失敗——等於白寫了這一步。

要修正這件事,有兩條路:改 Makefilefmt 目標讓它在有輸出時回傳非零,或是在 workflow 裡自己展開判斷。前者會變成「為了 CI 反過來動本機開發工具」——make fmt 原本的用途就是「列出來讓人看」,不是「拿來當 CI gate」,硬改語意會讓本機開發者對 make fmt 的預期跟著改變。所以選擇在 ci.yml 裡直接展開四個獨立步驟:gofmt -l .go vet ./...go test ./...go run ./cmd/brain healthgofmt 這一步額外包一層「輸出是否為空」的判斷再決定要不要 exit 1Makefile 本身完全不用碰。

brain health 的失敗判定,CI 不多加一條規則

validate job 的最後一步是 go run ./cmd/brain health,對 vault/ 跑一次健康檢查。這裡刻意不在 CI 層新增任何判斷邏輯——brain health 的結束碼語意在 Day11 就定案了:有斷鏈才非零,孤立筆記不影響結束碼。這個語意本身已經是經過設計的產品決策:孤立筆記在寫作過程中是常見、暫時的正常狀態,不該被當成需要擋下 CI 的錯誤。

如果在 CI 裡另外加一條「孤立筆記超過幾篇就失敗」,等於是繞過 brain-cli 自己都還沒有共識的門檻,偷偷塞了一個新的產品決策進 CI workflow,而不是 brain-cli 這個工具本身。CI 只看 brain health 自己吐出的結束碼——它說過了才算過,它沒說失敗就不失敗,不多想。

圖譜快照選 workflow artifact,不進 git;視覺化另開一條 workflow

graph job 執行 go run ./cmd/brain graph --json,把輸出導向 graph.json,再用 actions/upload-artifact 上傳成這次 workflow run 的附件。考慮過的替代方案有兩個:把 graph.json commit 回 repo,或是進一步拿這份 JSON 做視覺化頁面部署到 GitHub Pages。

commit 回 repo 這條路放棄:這會讓「機器產生的檔案」變成版本控制歷史的一部分——每次任何一篇筆記異動,都會多一次機器產生的 diff,污染 git log,而 git log 正是 Day27 /weekly-report 賴以分類「知識庫異動」跟「工具開發異動」的依據,混進機器產生的 diff 反而會干擾這個分類邏輯。ci.ymlgraph job 因此只上傳 artifact,不寫回版本控制。

視覺化則交給另一個獨立的 pages.yml workflow 承接,不塞進 ci.ymlgraph job 裡:ci.yml 的職責維持單純(驗證程式碼品質、留一份原始 JSON 快照),部署到 GitHub Pages 這種會對外公開、需要額外套件與較長流程的工作獨立成另一條 workflow,觸發條件也分開(pages.yml 只在 pushmaster 且改動到 vault/cmd/internal/ 時才跑),彼此失敗互不牽連——pages.yml 哪一步失敗,只影響網站有沒有更新,不會連帶讓 validate/graph 這兩個既有的驗證 job 跟著失敗。

CI 不會去呼叫 /weekly-report,這條線刻意先劃在這裡

Day27 設計 /weekly-report 時寫了一句「排程與 CI 整合屬於 Day28 ci-cd-pipeline 的範疇」,這句話很容易被誤讀成「今天要讓 CI 自動觸發 /weekly-report」。今天要先把這個誤讀排除掉:/weekly-report(以及 /new-adr/new-postmortem/refine-inbox/ask-vault)都是 Claude Code slash command,運作依賴的是「當下對話脈絡」,不是可以被 GitHub Actions headless 呼叫的確定性指令。沒有對話脈絡,這些指令要嘛需要 Claude Code runtime 而完全無法在純 CI 環境執行,要嘛就算硬跑也只會產生「待補充」滿版的空洞筆記,然後不經任何人審閱就自動落地進 vault。

這正是 Day29(anti-pattern-playbook,反模式回顧)要專門討論的過度自動化風險。今天刻意不解決這個問題,只把邊界劃清楚:CI 涵蓋的自動化範圍僅限於 brain-cli 這一層的確定性指令,Agent 工作流程那一層留給明天。ci.yml 裡沒有、也不會出現任何呼叫 claude CLI 或 .claude/commands/*.md 定義指令的步驟。

本機模擬跑一次,兩個 job 的預期輸出

無法真的觸發一次 GitHub Actions,但可以在本機把 validate/graph 兩個 job 的每一步驟原樣跑一次,確認 workflow 定義的指令組合是有效的:

validate job:

$ gofmt -l .
(無輸出)

$ go vet ./...
(無輸出,結束碼 0)

$ go test ./...
ok  	github.com/yuanyu90221/obsidian-agent-brain/...	0.xxxs

$ go run ./cmd/brain health
孤立筆記:9 篇
斷鏈:0 條
(結束碼 0)

四步都通過,validate job 會標記成功。另外手動在 cmd/brain/main.go 尾端加了幾行空白製造格式問題,重跑 gofmt -l . 確認會列出該檔名——驗證完立刻還原,git status --short 確認除了 .github/ 之外沒有殘留異動。

graph job:

$ go run ./cmd/brain graph --json > /tmp/graph.json
$ cat /tmp/graph.json | python3 -m json.tool | head -3
{
    "nodes": [...],
    "edges": [...],

輸出的 nodeCount/edgeCount 跟同一時間跑 brain scan --json 回報的筆記數(21 篇有效筆記、1 篇既有的解析錯誤 vault/README.md 不列入節點)互相對得起來,graph job 會產出一份可信的快照上傳成 artifact。

多一種 JSON 輸出格式,驗證的標準也要跟著提高

brain graph --json 之外,後來又替 brain graph 加了一個 --graphify 輸出格式:把同一份筆記連結圖,轉成 graphify skill 的 extraction schema(nodes/edges/hyperedges,欄位命名、confidence 等級都對應 graphify 抽取子代理自己的產出格式),目的是讓 vault 的連結圖可以直接餵進 graphify 既有的 build_from_json() 管線,換取社群分群、視覺化、query/path/explain 這些下游能力,不必讓 graphify 重新掃一次 vault 原始檔案。

這讓「JSON 合不合法」的門檻整個提高了。--json 的輸出只要「懂圖論的人看得懂」就算過關;--graphify 的輸出卻是要餵給另一個工具直接解析——欄位名稱打錯、confidence 給錯型別、node ID 混進 schema 不允許的字元,brain graph 自己完全不會報錯,卻會讓下游在完全不同的 repo、完全不同的時間點解析失敗,而且錯誤已經離「真正寫錯的地方」隔了一層,追起來比 CI 當場失敗麻煩得多。

驗證因此拆成兩層,對應「轉換邏輯本身對不對」跟「最終輸出乾不乾淨」兩個不同的問題:

  1. 結構層:不透過 CLI,直接測負責轉換的函式本身——node ID 是否落在 graphify 規定的字元集內、中文標題的筆記轉換後 ID 是否仍然唯一不撞號、解不開的 wikilink 是否老實跳過而不是產生指向不存在節點的邊、每條邊的來源/目標是否都能在節點清單裡找到對應項目。
  2. 輸出層:實際呼叫 brain graph --graphify 這條 CLI 路徑,把 stdout 整段還原成對應的 struct——驗證的不是轉換邏輯對不對,而是這條路徑組裝出來的最終輸出,是不是乾淨到可以被下游直接反序列化:stdout 有沒有混進其他文字、欄位型別是否符合預期。

兩層合起來保證的是同一件事的兩個面向:邏輯內部對,加上對外輸出乾淨——這正是後續工作流程敢把 brain graph --graphify 的輸出直接交給下游解析、中途不需要人工檢查這一步的前提。

銜接後續

ci.yml 讓「push 就自動驗證」「順手留一份圖譜快照」不再需要有人記得手動跑,但它同時也刻意留下一條沒解決的線:Agent 工作流程(/weekly-report 這類 slash command)依然只能手動呼叫,排程觸發缺的是「對話脈絡」——沒有人在跟它聊天,## 下週待辦 這種需要參考對話的欄位就沒有東西可以填。Day29 會回頭檢視這整個系列從 Day15 到今天疊出來的自動化堆疊,討論哪些地方看似可以「乾脆全部自動化」,但其實需要人在中間卡一道審閱關卡——今天刻意劃清楚的這條邊界,就是那個討論的起點。


上一篇
研發實戰:結合 Git Commit 與 PR,自動生成週報與技術脈絡
系列文
打造 AI Agent 驅動的第二大腦:用 Go + Claude Code + Obsidian + Graphify 打造工程師知識作業系統28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言